package com.cencat.common.utils;

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.cencat.common.utils.FileUtils;
import com.cencat.common.utils.CencatStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

/**
 * 二维码工具类
 * 提供二维码的生成、解析、美化等功能
 * 
 * @author cencat
 * @since 2024-01-01
 */

public class QRCodeUtils {

    private static final Logger log = LoggerFactory.getLogger(QRCodeUtils.class);

    private QRCodeUtils() {
        // 私有构造函数，防止实例化
    }

    /**
     * 生成二维码（默认参数）
     * 
     * @param content 二维码内容
     * @param width   宽度
     * @param height  高度
     * @return 二维码图片
     */
    public static BufferedImage generateQRCode(String content, int width, int height) {
        return generateQRCode(content, width, height, QRCodeConfig.defaultConfig());
    }

    /**
     * 生成二维码（自定义配置）
     * 
     * @param content 二维码内容
     * @param width   宽度
     * @param height  高度
     * @param config  配置参数
     * @return 二维码图片
     */
    public static BufferedImage generateQRCode(String content, int width, int height, QRCodeConfig config) {
        try {
            // 创建二维码写入器
            QRCodeWriter qrCodeWriter = new QRCodeWriter();
            
            // 设置编码参数
            Map<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.CHARACTER_SET, config.getCharset());
            hints.put(EncodeHintType.ERROR_CORRECTION, config.getErrorCorrectionLevel());
            hints.put(EncodeHintType.MARGIN, config.getMargin());
            
            // 生成二维码矩阵
            BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
            
            // 转换为图片
            BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
            
            // 如果有Logo，添加Logo
            if (config.getLogoImage() != null) {
                qrImage = addLogo(qrImage, config.getLogoImage(), config.getLogoSize());
            }
            
            return qrImage;
        } catch (WriterException e) {
            log.error("生成二维码失败: content={}, error={}", content, e.getMessage());
            throw new RuntimeException("生成二维码失败: " + e.getMessage());
        }
    }

    /**
     * 生成二维码并保存到文件
     * 
     * @param content  二维码内容
     * @param width    宽度
     * @param height   高度
     * @param filePath 文件路径
     */
    public static void generateQRCodeToFile(String content, int width, int height, String filePath) {
        generateQRCodeToFile(content, width, height, filePath, QRCodeConfig.defaultConfig());
    }

    /**
     * 生成二维码并保存到文件（自定义配置）
     * 
     * @param content  二维码内容
     * @param width    宽度
     * @param height   高度
     * @param filePath 文件路径
     * @param config   配置参数
     */
    public static void generateQRCodeToFile(String content, int width, int height, String filePath, QRCodeConfig config) {
        try {
            BufferedImage qrImage = generateQRCode(content, width, height, config);
            
            // 获取文件格式
            String format = getImageFormat(filePath);
            
            // 保存到文件
            File outputFile = new File(filePath);
            // 确保父目录存在
            File parentDir = outputFile.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                parentDir.mkdirs();
            }
            
            ImageIO.write(qrImage, format, outputFile);
            log.info("二维码已保存到文件: {}", filePath);
        } catch (IOException e) {
            log.error("保存二维码文件失败: filePath={}, error={}", filePath, e.getMessage());
            throw new RuntimeException("保存二维码文件失败: " + e.getMessage());
        }
    }

    /**
     * 生成二维码并输出到流
     * 
     * @param content      二维码内容
     * @param width        宽度
     * @param height       高度
     * @param outputStream 输出流
     * @param format       图片格式
     */
    public static void generateQRCodeToStream(String content, int width, int height, 
                                              OutputStream outputStream, String format) {
        generateQRCodeToStream(content, width, height, outputStream, format, QRCodeConfig.defaultConfig());
    }

    /**
     * 生成二维码并输出到流（自定义配置）
     * 
     * @param content      二维码内容
     * @param width        宽度
     * @param height       高度
     * @param outputStream 输出流
     * @param format       图片格式
     * @param config       配置参数
     */
    public static void generateQRCodeToStream(String content, int width, int height, 
                                              OutputStream outputStream, String format, QRCodeConfig config) {
        try {
            BufferedImage qrImage = generateQRCode(content, width, height, config);
            ImageIO.write(qrImage, format, outputStream);
        } catch (IOException e) {
            log.error("输出二维码到流失败: content={}, error={}", content, e.getMessage());
            throw new RuntimeException("输出二维码到流失败: " + e.getMessage());
        }
    }

    /**
     * 解析二维码
     * 
     * @param qrImage 二维码图片
     * @return 二维码内容
     */
    public static String parseQRCode(BufferedImage qrImage) {
        try {
            // 创建二维码读取器
            MultiFormatReader reader = new MultiFormatReader();
            
            // 设置解码参数
            Map<DecodeHintType, Object> hints = new HashMap<>();
            hints.put(DecodeHintType.CHARACTER_SET, QRCodeConstants.DEFAULT_CHARSET);
            hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
            hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
            
            // 创建亮度源
            LuminanceSource source = new BufferedImageLuminanceSource(qrImage);
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            
            // 解码
            Result result = reader.decode(bitmap, hints);
            return result.getText();
        } catch (NotFoundException e) {
            log.error("解析二维码失败: error={}", e.getMessage());
            throw new RuntimeException("解析二维码失败: " + e.getMessage());
        }
    }

    /**
     * 从文件解析二维码
     * 
     * @param filePath 文件路径
     * @return 二维码内容
     */
    public static String parseQRCodeFromFile(String filePath) {
        try {
            BufferedImage qrImage = ImageIO.read(new File(filePath));
            return parseQRCode(qrImage);
        } catch (IOException e) {
            log.error("从文件解析二维码失败: filePath={}, error={}", filePath, e.getMessage());
            throw new RuntimeException("从文件解析二维码失败: " + e.getMessage());
        }
    }

    /**
     * 从输入流解析二维码
     * 
     * @param inputStream 输入流
     * @return 二维码内容
     */
    public static String parseQRCodeFromStream(InputStream inputStream) {
        try {
            BufferedImage qrImage = ImageIO.read(inputStream);
            return parseQRCode(qrImage);
        } catch (IOException e) {
            log.error("从输入流解析二维码失败: error={}", e.getMessage());
            throw new RuntimeException("从输入流解析二维码失败: " + e.getMessage());
        }
    }

    /**
     * 添加Logo到二维码
     * 
     * @param qrImage   二维码图片
     * @param logoImage Logo图片
     * @param logoSize  Logo大小（相对于二维码的比例，0.0-1.0）
     * @return 带Logo的二维码图片
     */
    private static BufferedImage addLogo(BufferedImage qrImage, BufferedImage logoImage, double logoSize) {
        try {
            int qrWidth = qrImage.getWidth();
            int qrHeight = qrImage.getHeight();
            
            // 计算Logo尺寸
            int logoWidth = (int) (qrWidth * logoSize);
            int logoHeight = (int) (qrHeight * logoSize);
            
            // 缩放Logo
            BufferedImage scaledLogo = new BufferedImage(logoWidth, logoHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = scaledLogo.createGraphics();
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.drawImage(logoImage, 0, 0, logoWidth, logoHeight, null);
            g2d.dispose();
            
            // 创建带Logo的二维码
            BufferedImage combinedImage = new BufferedImage(qrWidth, qrHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = combinedImage.createGraphics();
            g.drawImage(qrImage, 0, 0, null);
            
            // 计算Logo位置（居中）
            int logoX = (qrWidth - logoWidth) / 2;
            int logoY = (qrHeight - logoHeight) / 2;
            
            // 绘制白色背景（圆角矩形）
            int padding = 5;
            g.setColor(Color.WHITE);
            g.fill(new RoundRectangle2D.Float(logoX - padding, logoY - padding, 
                                              logoWidth + 2 * padding, logoHeight + 2 * padding, 10, 10));
            
            // 绘制Logo
            g.drawImage(scaledLogo, logoX, logoY, null);
            g.dispose();
            
            return combinedImage;
        } catch (Exception e) {
            log.error("添加Logo失败: error={}", e.getMessage());
            return qrImage; // 返回原始二维码
        }
    }

    /**
     * 获取图片格式
     * 
     * @param filePath 文件路径
     * @return 图片格式
     */
    private static String getImageFormat(String filePath) {
        String extension = FileUtils.getFileExtension(filePath);
        if (CencatStringUtils.isBlank(extension)) {
            return QRCodeConstants.DEFAULT_FORMAT;
        }
        
        switch (extension.toLowerCase()) {
            case "jpg":
            case "jpeg":
                return "JPEG";
            case "png":
                return "PNG";
            case "gif":
                return "GIF";
            case "bmp":
                return "BMP";
            default:
                return QRCodeConstants.DEFAULT_FORMAT;
        }
    }

    /**
     * 验证二维码内容长度
     * 
     * @param content 二维码内容
     * @return 是否有效
     */
    public static boolean isValidContent(String content) {
        if (CencatStringUtils.isBlank(content)) {
            return false;
        }
        return content.length() <= QRCodeConstants.MAX_CONTENT_LENGTH;
    }

    /**
     * 验证二维码尺寸
     * 
     * @param width  宽度
     * @param height 高度
     * @return 是否有效
     */
    public static boolean isValidSize(int width, int height) {
        return width >= QRCodeConstants.MIN_SIZE && width <= QRCodeConstants.MAX_SIZE &&
               height >= QRCodeConstants.MIN_SIZE && height <= QRCodeConstants.MAX_SIZE;
    }

    /**
     * 创建二维码配置构建器
     * 
     * @return 配置构建器
     */
    public static QRCodeConfig.Builder configBuilder() {
        return new QRCodeConfig.Builder();
    }

    /**
     * 二维码配置类
     */
    public static class QRCodeConfig {
        private String charset = QRCodeConstants.DEFAULT_CHARSET;
        private ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.M;
        private int margin = QRCodeConstants.DEFAULT_MARGIN;
        private BufferedImage logoImage;
        private double logoSize = QRCodeConstants.DEFAULT_LOGO_SIZE;

        private QRCodeConfig() {
        }

        public static QRCodeConfig defaultConfig() {
            return new QRCodeConfig();
        }

        public String getCharset() {
            return charset;
        }

        public ErrorCorrectionLevel getErrorCorrectionLevel() {
            return errorCorrectionLevel;
        }

        public int getMargin() {
            return margin;
        }

        public BufferedImage getLogoImage() {
            return logoImage;
        }

        public double getLogoSize() {
            return logoSize;
        }

        /**
         * 配置构建器
         */
        public static class Builder {
            private final QRCodeConfig config = new QRCodeConfig();

            public Builder charset(String charset) {
                config.charset = charset;
                return this;
            }

            public Builder errorCorrectionLevel(ErrorCorrectionLevel level) {
                config.errorCorrectionLevel = level;
                return this;
            }

            public Builder margin(int margin) {
                config.margin = margin;
                return this;
            }

            public Builder logo(BufferedImage logoImage) {
                config.logoImage = logoImage;
                return this;
            }

            public Builder logo(String logoPath) {
                try {
                    config.logoImage = ImageIO.read(new File(logoPath));
                } catch (IOException e) {
                    log.error("加载Logo图片失败: logoPath={}, error={}", logoPath, e.getMessage());
                }
                return this;
            }

            public Builder logo(InputStream logoStream) {
                try {
                    config.logoImage = ImageIO.read(logoStream);
                } catch (IOException e) {
                    log.error("从流加载Logo图片失败: error={}", e.getMessage());
                }
                return this;
            }

            public Builder logoSize(double logoSize) {
                if (logoSize > 0 && logoSize <= 1.0) {
                    config.logoSize = logoSize;
                }
                return this;
            }

            public QRCodeConfig build() {
                return config;
            }
        }
    }

    /**
     * 二维码常量类
     */
    public static class QRCodeConstants {
        
        // 默认配置
        public static final String DEFAULT_CHARSET = "UTF-8";
        public static final String DEFAULT_FORMAT = "PNG";
        public static final int DEFAULT_MARGIN = 1;
        public static final double DEFAULT_LOGO_SIZE = 0.2; // Logo占二维码的20%
        
        // 尺寸限制
        public static final int MIN_SIZE = 50;
        public static final int MAX_SIZE = 2000;
        public static final int DEFAULT_SIZE = 300;
        
        // 内容长度限制
        public static final int MAX_CONTENT_LENGTH = 2000;
        
        // 支持的图片格式
        public static final String[] SUPPORTED_FORMATS = {"PNG", "JPEG", "JPG", "GIF", "BMP"};
        
        // 错误纠正级别说明
        // L: 约7%的错误纠正能力
        // M: 约15%的错误纠正能力（默认）
        // Q: 约25%的错误纠正能力
        // H: 约30%的错误纠正能力
        
        private QRCodeConstants() {
            // 私有构造函数，防止实例化
        }
    }

    /**
     * 二维码生成结果类
     */
    public static class QRCodeResult {
        private boolean success;
        private String message;
        private BufferedImage image;
        private String filePath;
        private long fileSize;

        public QRCodeResult() {
        }

        public QRCodeResult(boolean success, String message) {
            this.success = success;
            this.message = message;
        }

        public static QRCodeResult success(BufferedImage image) {
            QRCodeResult result = new QRCodeResult(true, "二维码生成成功");
            result.setImage(image);
            return result;
        }

        public static QRCodeResult success(String filePath, long fileSize) {
            QRCodeResult result = new QRCodeResult(true, "二维码生成成功");
            result.setFilePath(filePath);
            result.setFileSize(fileSize);
            return result;
        }

        public static QRCodeResult failure(String message) {
            return new QRCodeResult(false, message);
        }

        // Getters and Setters
        public boolean isSuccess() {
            return success;
        }

        public void setSuccess(boolean success) {
            this.success = success;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public BufferedImage getImage() {
            return image;
        }

        public void setImage(BufferedImage image) {
            this.image = image;
        }

        public String getFilePath() {
            return filePath;
        }

        public void setFilePath(String filePath) {
            this.filePath = filePath;
        }

        public long getFileSize() {
            return fileSize;
        }

        public void setFileSize(long fileSize) {
            this.fileSize = fileSize;
        }
    }
}